package com.cn.qtms.service.impl;//类注释设置模板

import com.cn.qtms.base.dao.BaseDao;
import com.cn.qtms.base.service.impl.BaseServiceImpl;
import com.cn.qtms.common.codename.CodeNameItem;
import com.cn.qtms.common.page.PageReq;
import com.cn.qtms.common.page.PageRes;
import com.cn.qtms.common.result.QueryParam;
import com.cn.qtms.common.result.ResultUtil;
import com.cn.qtms.controller.codename.CodeNameController;
import com.cn.qtms.dao.TmSecGoodsDao;
import com.cn.qtms.entity.*;
import com.cn.qtms.exception.AppException;
import com.cn.qtms.service.TmGoodsPriceService;
import com.cn.qtms.service.TmGoodsService;
import com.cn.qtms.service.TmSecActService;
import com.cn.qtms.service.TmSecGoodsService;
import com.cn.qtms.service.codename.CodeNameService;
import com.cn.qtms.util.BeanUtils;
import com.cn.qtms.util.DateUtils;
import com.cn.qtms.vo.SecGoodsVo;
import com.cn.qtms.vo.SecWeeksVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.sql.Time;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @Name:TmSecGoodsServiceImpl
 * @Description:
 * @Author: zhangyang
 * @Date: 2019-12-25 19:23
 **/
@Service
@Slf4j
public class TmSecGoodsServiceImpl extends BaseServiceImpl<TmSecGoods, Long> implements TmSecGoodsService {
    @Autowired
    private TmSecActService actService;
    @Autowired
    private CodeNameService codeNameService;
    @Autowired
    private TmGoodsService goodsService;
    @Autowired
    private TmGoodsPriceService goodsPriceService;
    @Autowired
    private TmSecGoodsDao dao;
    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public BaseDao<TmSecGoods, Long> getDao() {
        return dao;
    }

    /**
     * id方法后续 星期拆分为多个集合
     *
     * @param tmSecGoods
     */
    @Override
    public void afterSearch(TmSecGoods tmSecGoods) {
        if (tmSecGoods.getWeek() != null) {
            final String[] split = tmSecGoods.getWeek().split(",");
            tmSecGoods.setWeekDays(Arrays.asList(split));
        }
        super.afterSearch(tmSecGoods);
    }

    @Override
    @Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)
    public TmSecGoods create(TmSecGoods tmSecGoods) {
        //1.数据非空校验
        log.info("now method TmSecGoodsService.create start check request param");
        final String[] typeTimes = this.getTypeTimes(tmSecGoods.getActType());
        if (typeTimes.length != 2) {
            throw new AppException("当前选择的限购时间段有误");
        }
        tmSecGoods.setStartTime(DateUtils.parse(typeTimes[0], "HH:mm:ss"));
        tmSecGoods.setEndTime(DateUtils.parse(typeTimes[1], "HH:mm:ss"));
        //商品校验
        final TmGoods byId = goodsService.findById(tmSecGoods.getGoodsId());
        if (byId == null || byId.getId() == null) {
            throw new AppException("当前商品不存在");
        }

        //判断当前商品是否是只能购买一次
        if (byId.getIsRestrict()) {
            if (!tmSecGoods.getIsLimit()) {
                throw new AppException("只能购买一次的商品,不能设置秒杀不限购");
            } else {
                if (tmSecGoods.getLimitNo() > 1) {
                    throw new AppException("只能购买一次的商品,限购只能设置1");
                }
            }
        }
        if (tmSecGoods.getWeekDays() == null || tmSecGoods.getWeekDays().size() <= 0) {
            throw new AppException("当前星期选择不能为空");
        }
        //活动校验
        final TmSecAct secAct = actService.findById(tmSecGoods.getActId());
        if (secAct == null || secAct.getId() == null) {
            throw new AppException("当前活动配置有误");
        }
        if ("2".equals(secAct.getStatus())) {
            throw new AppException("当前活动已经下架不能配置商品");
        }
        // 活动校验 避免相同商品多次配置价格的情况 校验判断商品+秒杀场次 是否存在活动日期重复配置的情况
        for (String s : tmSecGoods.getWeekDays()) {
            //出现查询sql情况 但是不严重只会出现周一到周日
            final Integer integer = dao.checkSecGoods(tmSecGoods.getActType(), tmSecGoods.getGoodsId(), s, secAct.getStartDate(), secAct.getEndDate());
            if (integer > 0) {
                throw new AppException("当前商品已经配置过场次秒杀信息，不能重复配置");
            }
        }
        //计算周期内会出现多少次活动
        final SecWeeksVo secWeeksVo = new SecWeeksVo(tmSecGoods.getActId(), tmSecGoods.getWeekDays());
        tmSecGoods.setWeek(secWeeksVo.getWeeksString(tmSecGoods.getWeekDays()));
        final Integer weeks = this.calWeeks(secWeeksVo);
        //比较 单次库存*数量 与前端传递过来的活动结束总消耗库存
        if (tmSecGoods.getEachCount() * weeks != tmSecGoods.getTotalCount()) {
            throw new AppException("当前库存总量计算有误");
        }
        //设置剩余库存和总库存相等
        tmSecGoods.setCurrentsCount(tmSecGoods.getTotalCount());
        //设置商品为默认
        tmSecGoods.setStatus("0");
        this.save(tmSecGoods);
        //扣除该种商品的商品可销售库存
        byId.setCurrents(byId.getCurrents() - tmSecGoods.getTotalCount());
        goodsService.save(byId);
        return tmSecGoods;
    }

    /**
     * 计算活动在指定配置星期会有多少次活动
     *
     * @param vo
     * @return
     */
    @Override
    public Integer calWeeks(SecWeeksVo vo) {
        log.info("now start calWeeks->actId=" + vo.getActId() + "vo");
        if (vo.getActId() == null || vo.getActId().longValue() == 0) {
            throw new AppException("活动id不能为空");
        }
        if (vo.getWeekDays() == null || vo.getWeekDays().size() == 0) {
            throw new AppException("活动周期不能为空");
        }
        final TmSecAct secAct = actService.findById(vo.getActId());
        //有可能bug  数据库时间和当前时间错位
        if (secAct.getStartDate().before(new Date()) || secAct.getEndDate().before(new Date())) {
            throw new AppException("活动必须未开始才能配置商品");
        }
        final int weeks = this.calculateWeeks(secAct.getStartDate(), secAct.getEndDate(), vo.getWeekDays());
        return weeks;
    }

    @Override
    public PageRes<SecGoodsVo> findSecGoods(QueryParam request) {

        //分页参数
        if (request.get("page") == null || request.get("size") == null) {
            throw new AppException("当前分页参数不能为空");
        }
        if (request.get("level") == null || request.get("index") == null) {
            throw new AppException("会员等级和场次不能为空");
        }
        Integer page = Integer.parseInt(request.get("page").toString());
        Integer size = Integer.parseInt(request.get("size").toString());
        if (page < 1 || size <= 0) {
            throw new AppException("分页参数必须符合要求");
        }
        //用户等级 建议是token获取
        final int level = Integer.parseInt(request.get("level").toString());
        //返回的前端4个分组下标
        final int index = Integer.parseInt(request.get("index").toString());
        if (level == 0 || index == 0) {
            throw new AppException("当前用户等级和所选区间不能为空");
        }
        //根据当前时间计算出和下标的位置获取所选场次
        final List<CodeNameItem> collect = codeNameService.nowActType().stream().filter(item -> item.getValue().equals(index)).collect(Collectors.toList());
        if (collect == null || collect.size() <= 0) {
            throw new AppException("当前输入的场次有误");
        }
        //redis的键值
        String redis_key = collect.get(0).getName();
        return buildRedisResult(redis_key, page, size, level);
    }

    PageRes<SecGoodsVo> buildRedisResult(String key, Integer page, Integer size, Integer level) {
        PageRes<SecGoodsVo> res = new PageRes<>();
        final Long redis_size = redisTemplate.opsForList().size(key);
        if (redis_size.intValue() <= size) {
            final List<SecGoodsVo> range = (List<SecGoodsVo>) redisTemplate.opsForList().range(key, 0, redis_size);

            res.setContent(buildContent(key, range, level));
            res.setPageIndex(page);
            res.setPageSize(size);
            res.setTotal(redis_size);
            res.setPageTotal(1);
        } else {
            //至少2页
            int pageTotal = redis_size.intValue() % size == 0 ? redis_size.intValue() / size : redis_size.intValue() / size + 1;
            final List<SecGoodsVo> range = (List<SecGoodsVo>) redisTemplate.opsForList().range(key, (page - 1) * size, page * size - 1);
            res.setContent(buildContent(key, range, level));
            res.setPageIndex(page);
            res.setPageSize(size);
            res.setTotal(redis_size);
            res.setPageTotal(pageTotal);
        }

        return res;
    }

    /**
     * 补全content方法
     *
     * @param key   redis键值
     * @param list  分页后的list
     * @param level 用户等级
     * @return
     */
    List<SecGoodsVo> buildContent(String key, List<SecGoodsVo> list, Integer level) {
        List<SecGoodsVo> result = new ArrayList();
        list.stream().forEach(item -> {
            //获取昨天最后一场的数据
            if (key.startsWith("yesterday_")) {
                String yes_key = "secgoods_" + item.getGoodsId() + "_" + 0;
                if (redisTemplate.hasKey(yes_key)) {
                    final TmSecKill o = (TmSecKill) redisTemplate.opsForValue().get(yes_key);
                    item.setUserLevel(level);
                    item.setFreeCount(o.getEndCount());
                    item.setSoldRate(bs(o.getEndCount(), o.getTotalCount()));
                    item.setCanBuy(level >= item.getMinUserLevel() ? 1 : 2);
                    result.add(item);
                } else {
                    item.setUserLevel(level);
                    item.setFreeCount(item.getEachCount());
                    item.setSoldRate(0);
                    item.setCanBuy(level >= item.getMinUserLevel() ? 1 : 2);
                    result.add(item);
                }
            } else if (key.startsWith("today_")) {
                String today_key = "secgoods_" + item.getGoodsId() + "_" + item.getActType();
                if (redisTemplate.hasKey(today_key)) {
                    final TmSecKill o = (TmSecKill) redisTemplate.opsForValue().get(today_key);
                    item.setUserLevel(level);
                    item.setFreeCount(o.getEndCount());
                    item.setSoldRate(bs(o.getEndCount(), o.getTotalCount()));
                    item.setCanBuy(level >= item.getMinUserLevel() ? 1 : 2);
                    result.add(item);
                } else {
                    item.setUserLevel(level);
                    item.setFreeCount(item.getEachCount());
                    item.setSoldRate(0);
                    item.setCanBuy(level >= item.getMinUserLevel() ? 1 : 2);
                    result.add(item);
                }
            } else {
                item.setUserLevel(level);
                item.setFreeCount(item.getEachCount());
                item.setSoldRate(0);
                item.setCanBuy(level >= item.getMinUserLevel() ? 1 : 2);
                result.add(item);
            }

        });
        return result;
    }

    @Override
    public void initSecGoods() {
        log.info("=============获取昨日秒杀商品列表============");
        final Calendar yester = Calendar.getInstance();
        yester.set(Calendar.HOUR_OF_DAY, -24);
        final String before = codeNameService.getWeek(yester);
        //redis键 yesterday_actType
        final Set yes_keys = redisTemplate.keys("yesterday_*");
        redisTemplate.delete(yes_keys);
        this.setSecGoodsToRedis("yesterday", before);
        log.info("=============获取今日秒杀商品列表============");
        final Calendar now = Calendar.getInstance();
        final String today = codeNameService.getWeek(now);
        //redis键 today_actType
        final Set today_keys = redisTemplate.keys("today_*");
        redisTemplate.delete(today_keys);
        this.setSecGoodsToRedis("today", today);
        log.info("=============获取明日秒杀商品列表============");
        final Calendar tom = Calendar.getInstance();
        tom.set(Calendar.HOUR_OF_DAY, 24);
        final String after = codeNameService.getWeek(tom);
        //redis键 tomorrow_actType
        final Set tomorrow_key = redisTemplate.keys("tomorrow_*");
        redisTemplate.delete(tomorrow_key);
        this.setSecGoodsToRedis("tomorrow", after);
    }

    /**
     * 将数据库数据存储到redis
     *
     * @param key
     * @param week
     */
    public void setSecGoodsToRedis(String key, String week) {
        final List<Map<String, Object>> todayGoods = dao.getSecGoods(week);
        BeanUtils<SecGoodsVo> utils = new BeanUtils<>();
        if (todayGoods != null) {
            //将集合按照actType分组
            final Map<Object, List<Map<String, Object>>> today = todayGoods.stream().collect(Collectors.groupingBy(t -> t.get("act_type")));
            for (Object obj : today.keySet()) {
                final List<SecGoodsVo> goodsVos = utils.parseListToVo(today.get(obj), new SecGoodsVo());
                //存储到缓存
                String redis_key = key + "_" + obj.toString();
                redisTemplate.opsForList().leftPushAll(redis_key, goodsVos);
                //设置缓存有效时间是23小时59分钟 保证正常运行时key不存在
                redisTemplate.expire(redis_key, 24 * 60 * 60, TimeUnit.SECONDS);
            }
        }
    }

    /**
     * 获取今日秒杀商品信息 不分页
     *
     * @return
     */
    @Override
    public List<TmSecGoods> getSecGoods() {
        //判断今日是星期几
        final Calendar today = Calendar.getInstance();
        final String week = codeNameService.getWeek(today);
        //获取今日秒杀商品信息
        final List<TmSecGoods> todaysecGoods = dao.getTodaysecGoods(week);
        return todaysecGoods;
    }

    /**
     * 计算已销售百分比
     *
     * @param free
     * @param total
     * @return
     */
    public int bs(int free, int total) {
        return (int) ((new BigDecimal((float) (total - free) / total).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue()) * 100);
    }

    /**
     * 计算开始时间到结束时间移动有多少次活动
     *
     * @param startDate
     * @param endDate
     * @param weeks
     * @return
     */
    public int calculateWeeks(Date startDate, Date endDate, List<String> weeks) {
        int result = 0;
        Calendar start = Calendar.getInstance();
        start.setTime(startDate);
        Calendar end = Calendar.getInstance();
        end.setTime(endDate);
        final Set<Integer> set = changeWeeks(weeks);
        while (start.before(end)) {
            if (set.contains(start.get(Calendar.DAY_OF_WEEK))) {
                //计数+1
                result++;
            }
            //时间+1
            start.add(Calendar.DATE, 1);
        }
        return result;
    }

    /**
     * 将前端传入的星期多选框转化为Calendar星期
     *
     * @param weeks
     * @return
     */
    public Set<Integer> changeWeeks(List<String> weeks) {
        Set<Integer> calWeeks = new HashSet<>();
        weeks.forEach(item -> {
            if (TmSecGoods.SUN_DAY.equals(item)) {
                calWeeks.add(Calendar.SUNDAY);
            }
            if (TmSecGoods.MON_DAY.equals(item)) {
                calWeeks.add(Calendar.MONDAY);
            }
            if (TmSecGoods.TUES_DAY.equals(item)) {
                calWeeks.add(Calendar.TUESDAY);
            }
            if (TmSecGoods.WED_DAY.equals(item)) {
                calWeeks.add(Calendar.WEDNESDAY);
            }
            if (TmSecGoods.THUR_DAY.equals(item)) {
                calWeeks.add(Calendar.THURSDAY);
            }
            if (TmSecGoods.FRI_DAY.equals(item)) {
                calWeeks.add(Calendar.FRIDAY);
            }
            if (TmSecGoods.SAT_DAY.equals(item)) {
                calWeeks.add(Calendar.SATURDAY);
            }
        });
        return calWeeks;
    }

    /**
     * 根据活动场次获取活动时间
     *
     * @param actType
     * @return
     */
    public String[] getTypeTimes(Integer actType) {
        final List<CodeNameItem> codeNameItems = codeNameService.actTypes();
        StringBuilder builder = new StringBuilder();
        codeNameItems.forEach(item -> {
            if (item.getValue().equals(actType)) {
                builder.append(item.getDesc());
            }
        });
        return builder.toString().split("-");
    }

}
